/**
 * Project Name:myutils
 * Date:2018年12月16日
 * Copyright (c) 2018, jingma All Rights Reserved.
 */

package cn.benma666.sjsj.web;

import cn.benma666.constants.SysAuth;
import cn.benma666.constants.UtilConst;
import cn.benma666.dict.Cllx;
import cn.benma666.dict.LjqType;
import cn.benma666.domain.SysQxYhxx;
import cn.benma666.domain.SysSjglFile;
import cn.benma666.domain.SysSjglSjdx;
import cn.benma666.exception.MyException;
import cn.benma666.iframe.*;
import cn.benma666.json.JsonUtil;
import cn.benma666.myutils.StringUtil;
import cn.benma666.params.Sys;
import cn.benma666.sjsj.myutils.MyTransactional;
import cn.benma666.sjsj.myutils.ThreadPool;
import cn.benma666.sjzt.Db;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.alibaba.fastjson.JSONObject;
import org.beetl.sql.core.DSTransactionManager;
import org.beetl.sql.core.SQLManager;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

import static cn.benma666.constants.UtilConstInstance.*;

/**
 * 系统-数据管理-拦截器管理 <br/>
 * date: 2018年12月16日 <br/>
 * @author jingma
 */
@Component
public class LjqManager extends BasicObject {
    /**
     * 日志对象
     */
    private static final Log log = LogFactory.get();
    /**
     * spring容器，可以从中获取注册的bean
     */
    private static ApplicationContext context;
    /**
     * 虚拟对象
     */
    private static SysSjglSjdx xndx;
    /**
     * 拦截器map<ljqStr,拦截器实例>
     */
    private static Map<String,LjqInterface> ljqMap=new HashMap<>();
    /**
     * http请求响应对象
     */
    private static final ThreadLocal<HttpServletResponse> httpServletResponseITL = new ThreadLocal<>();
    /**
     * http请求对象
     */
    private static final ThreadLocal<HttpServletRequest> HttpServletRequestITL = new ThreadLocal<>();
    /**
     * 参数对象的线程变量
     */
    private static final ThreadLocal<MyParams> mpITL = new ThreadLocal<>();

    /**
     * @return 当前线程的参数对象，若当前接口可能被其他接口内部调用则请不要使用本方法，可能导致错乱，获取其他接口的参数
     */
    public static MyParams getMyParams(){
        return mpITL.get();
    }

    /**
     * 设置当前线程的参数对象
     */
    public static void setMyParams(MyParams myParams){
        if(myParams==null){
            mpITL.remove();
        }else {
            mpITL.set(myParams);
        }
    }

    /**
     * 获取当前线程数据对象的数据库工具，若当前接口可能被其他接口内部调用则请不要使用本方法，可能导致错乱
     */
    public static Db getDb(){
        return Db.db(getMyParams().sjdx().getDxzt());
    }

    /**
     * 获取当前线程数据对象的SqlManager，若当前接口可能被其他接口内部调用则请不要使用本方法，可能导致错乱
     */
    public static SQLManager getSqlManager(){
        return getDb().getSqlManager();
    }

    public LjqManager(ApplicationContext context){
        LjqManager.context = context;
        //给虚拟对象赋值
        xndx = new SysSjglSjdx();
        xndx.setId("912769435779405698B51852FB4277DB");
    }
    /**
     * 使用对应的拦截器 <br/>
     * 对象中配置的拦截器>对象代码注解设置的拦截器>默认拦截器
     * @param sjdx 数据对象
     * @param ljqType 拦截器类型
     * @return 拦截器
     * @author jingma
     */
    private static LjqInterface use(SysSjglSjdx sjdx, LjqType ljqType) {
        if(sjdx==null){
            //没有数据对象就采用虚拟对象获取拦截器
            sjdx = xndx;
        }
        String ljqStr;
        switch (ljqType){
            case dzljq:
                ljqStr = sjdx.getLjq();
                if(isBlank(ljqStr)){
                    //没有在对象中配置拦截器
                    if(!isBlank(sjdx.getDxdm())){
                        ljqStr = sjdx.getDxdm();
                    }else {
                        //没有定制拦截器
                        return null;
                    }
                }
                break;
            case dzmrlqj:
                ljqStr = Conf.getUtilConfig().getLjqDefault();
                if(isBlank(ljqStr)){
                    return null;
                }
                break;
            case kjmrljq:
                ljqStr = DefaultLjq.class.getName();
                break;
            default:
                throw new IllegalStateException("Unexpected value: " + ljqType);
        }
        LjqInterface ljq = ljqMap.get(ljqStr);
        if(ljq!=null){
            return ljq;
        }
        try{
            // 支持从spring容器中取拦截器，从而支持开发在拦截器中使用spring的注解
            ljq = (LjqInterface) context.getBean(ljqStr);
        }catch (NoSuchBeanDefinitionException t){
            //没有找到，忽略
            try{
                Class<?> ljqClass = Class.forName(ljqStr);
                try{
                    // 支持从spring容器中取拦截器，从而支持开发在拦截器中使用spring的注解
                    ljq = (LjqInterface) context.getBean(ljqClass);
                }catch (Throwable t1){
                    // 实例化定制拦截器
                    ljq = (LjqInterface) ljqClass.getConstructor().newInstance();
                }
            } catch (ClassNotFoundException e) {
                //没找类
                return null;
            } catch (Exception e) {
                slog.error("拦截器实例化失败："+e.getMessage(), e);
                throw new MyException("拦截器实例化失败："+e.getMessage(), e);
            }
        }
        ljqMap.put(ljqStr,ljq);
        ljq.init();
        return ljq;
    }

    /**
     * 获取需要执行的拦截器
     * @param ljqType 拦截器类型，默认定制拦截器
     * @param sjdx 数据对象
     * @param cllx 处理类型
     * @param myParams 参数对象
     * @return 拦截器
     * @throws NoSuchMethodException 找不到方法
     */
    private static LjqInterface getLjq(LjqType ljqType,SysSjglSjdx sjdx,String cllx,MyParams myParams) throws NoSuchMethodException {
        if(ljqType==null){
            ljqType = LjqType.dzljq;
        }
        LjqInterface ljq = use(sjdx,ljqType);
        try {
            if(ljq!=null){
                ljq.getClass().getMethod(cllx, MyParams.class);
                //该拦截器正常找到要执行的方法
                //每次调用后，默认都是定制拦截器，其他场景自己设置
                myParams.sys().setLjqType(LjqType.dzljq);
                return ljq;
            }
        }catch (NoSuchMethodException e){
            //没有找到该方法
        }
        switch (ljqType){
            case dzljq:
                return getLjq(LjqType.dzmrlqj,sjdx,cllx,myParams);
            case dzmrlqj:
                return getLjq(LjqType.kjmrljq,sjdx,cllx,myParams);
            case kjmrljq:
                throw new NoSuchMethodException("没有找到合适的拦截器");
        }
        return ljq;
    }
    /**
     * 获取数据对象基础信息 <br/>
     * @param id 数据对象id
     * @return 基础信息
     * @author jingma
     */
    public static MyParams jcxxById(String id) {
        return jcxxById(id,null);
    }
    /**
     * 获取数据对象基础信息 <br/>
     * @param id 数据对象id
     * @return 基础信息
     * @author jingma
     */
    public static MyParams jcxxById(String id, SysQxYhxx user) {
        MyParams myParams = new MyParams();
        myParams.sjdx().setId(id);
        return jcxxByParams(myParams,user);
    }
    /**
     * 获取数据对象基础信息 <br/>
     * @param dxdm 数据对象代码
     * @return 基础信息
     * @author jingma
     */
    public static MyParams jcxxByDxdm(String dxdm) {
        return jcxxByDxdm(dxdm,null);
    }
    public static MyParams jcxxByDxdm(String dxdm, SysQxYhxx user) {
        MyParams myParams = new MyParams(true);
        myParams.sjdx().setDxdm(dxdm);
        return jcxxByParams(myParams,user);
    }
    /**
     * 获取数据对象基础信息 <br/>
     * @param authCode 数据对象代码
     * @return 基础信息
     * @author jingma
     */
    public static MyParams jcxxByAuthCode(String authCode) {
        return jcxxByAuthCode(authCode,null);
    }
    public static MyParams jcxxByAuthCode(String authCode, SysQxYhxx user) {
        MyParams myParams = new MyParams(true);
        myParams.sys().setAuthCode(authCode);
        return jcxxByParams(myParams,user);
    }

    public static MyParams jcxxByParams(MyParams myParams, SysQxYhxx user) {
        return jcxxByParams(myParams,user,true);
    }
    /**
     * 自定义参数获取对象信息-内部调用
     * @param myParams 自定义参数
     * @param user 用户信息
     * @return 对象参数
     */
    public static MyParams jcxxByParams(MyParams myParams, SysQxYhxx user, boolean nbdy) {
        if(!myParams.containsKey(LjqInterface.KEY_YOBJ)){
            myParams.put(LjqInterface.KEY_YOBJ,new JSONObject());
        }
        myParams.sys().setNbdy(nbdy);
        //设置请求id
        myParams.sys().setQqid(StringUtil.getUUIDUpperStr());
        if(isBlank(myParams.sys().getCllx())){
            myParams.sys().setCllx(Cllx.dxjcxx.name());
        }
        if(myParams.other().getYsParams()==null){
            //此处一般是内部调用场景，自动设置基础参数备份
            myParams.other().setYsParams(JsonUtil.clone(myParams));
        }
        if(user!=null){
            myParams.put(KEY_USER,user);
        }
        return initSjdx(myParams);
    }

    /**
     * 数据处理 <br/>
     * @param myParams 相关参数
     * @return 基础信息
     * @author jingma
     */
    public static Result data(MyParams myParams) {
        return data(myParams,myParams.sjdx(),myParams.sys().getCllx(),myParams.sys().getLjqType());
    }
    /**
     * 数据处理 <br/>
     * @param myParams 相关参数
     * @return 基础信息
     * @param ljqType 拦截器类型
     * @author jingma
     */
    public static Result data(MyParams myParams,LjqType ljqType) {
        return data(myParams,myParams.sjdx(),myParams.sys().getCllx(),ljqType);
    }
    /**
     * 数据处理 <br/>
     * @param myParams 相关参数
     * @param cllx 处理类型
     * @return 基础信息
     * @author jingma
     */
    public static Result data(MyParams myParams,String cllx) {
        return data(myParams,myParams.sjdx(),cllx,myParams.sys().getLjqType());
    }
    /**
     * 数据处理 <br/>
     * @param myParams 相关参数
     * @param cllx 处理类型
     * @param ljqType 拦截器类型
     * @return 基础信息
     * @author jingma
     */
    public static Result data(MyParams myParams,String cllx,LjqType ljqType) {
        return data(myParams,myParams.sjdx(),cllx,ljqType);
    }
    /**
     * 数据处理 <br/>
     * @param myParams 相关参数
     * @param sjdx 数据对象
     * @param cllx 处理类型
     * @param ljqType 拦截器类型
     * @return 基础信息
     * @author jingma
     */
    public static Result data(MyParams myParams,SysSjglSjdx sjdx,String cllx,LjqType ljqType) {
        if(myParams.sys().getHdcst()!=null&&!(myParams.sys().getHdcst() instanceof HdInterface)){
            //有回调参数体，且不是回到方法，采用异步方式
            ThreadPool.use().run(()->{
                try {
                    LjqManager.setMyParams(myParams);
                    log.debug("异步请求结束：{}",data1(myParams,sjdx,cllx,ljqType));
                }catch (Throwable t){
                    log.warn("异步请求异常",t);
                }finally {
                    LjqManager.setMyParams(null);
                }
            });
            return success("异步请求成功，后续会采用回调方式返回结果数据");
        }
        //备份原有的参数对象，此处主要是考虑内部调用时，避免相互影响
        MyParams yMyParams = LjqManager.getMyParams();
        LjqManager.setMyParams(myParams);
        Result r = data1(myParams,sjdx, cllx,ljqType);
        //还原参数对象
        LjqManager.setMyParams(yMyParams);
        return r;
    }

    private static Result data1(MyParams myParams, SysSjglSjdx sjdx, String cllx, LjqType dzljq) {
        if(!isBlank(myParams.other().getYsParams())&&!Arrays.asList(Cllx.initSjdx.name(),
                Cllx.getUser.name(),Cllx.dxjcxx.name()).contains(cllx)){
            Sys ysSys = myParams.other().getYsParams().sys();
            if(!isBlank(ysSys.getCtqapp())&&!Conf.getAppdm()
                    .equals(ysSys.getCtqapp())){
                myParams.sys().setCtapp(ysSys.getCtqapp());
                ysSys.setCtqapp(null);
                return data1(myParams,sjdx,Cllx.ctapp.name(), LjqType.dzljq);
            }else if(!isBlank(ysSys.getCtdy())){
                ysSys.setCtdy(null);
                return data1(myParams,sjdx,Cllx.ctdy.name(), LjqType.dzljq);
            }else if(!isBlank(ysSys.getCtapp())&&!Conf.getAppdm()
                    .equals(ysSys.getCtapp())){
                //有穿透app，且不是当前app
                ysSys.setCtapp(null);
                return data1(myParams,sjdx,Cllx.ctapp.name(), LjqType.dzljq);
            }
        }
        try{
            //支持通过参数设置不走定制拦截器，以适应定制调用默认的需求
            LjqInterface ljq = getLjq(dzljq, sjdx, cllx,myParams);
            Method m = ljq.getClass().getMethod(cllx, MyParams.class);
            log.trace("开始执行方法：{}",cllx);
            if(m.getAnnotation(MyTransactional.class)!=null||m.getAnnotation(Transactional.class)!=null){
                //没管事务的其他参数
                DSTransactionManager.start();
                try {
                    Object r = m.invoke(ljq, myParams);
                    log.trace("结束执行方法-事务：{},{}",cllx,r);
                    return swtj((Result) r);
                }catch (Throwable e){
                    //回滚事务
                    swtj(failed("失败"));
                    if(MyException.class.isAssignableFrom(e.getClass())){
                        throw e;
                    }else if(e.getCause()!=null&&(e.getCause() instanceof MyException)){
                        throw (MyException)e.getCause();
                    }else {
                        throw new MyException("执行失败-事务："+cllx,e.getCause());
                    }
                }
            }else {
                return (Result) m.invoke(ljq, myParams);
            }
        } catch (NoSuchMethodException e){
            //没有找到该处理类型对应的方法，执行默认操作
            String zxcz = myParams.sys().getZxcz();
            if(isBlank(zxcz)||Cllx.getdata.name().equals(zxcz)){
                return data1(myParams,sjdx,Cllx.getdata.name(), LjqType.dzljq);
            }else if(Cllx.plcl.name().equals(zxcz)){
                return data1(myParams,sjdx,Cllx.plcl.name(), LjqType.dzljq);
            }else if(Cllx.getfile.name().equals(zxcz)){
                return data1(myParams,sjdx,Cllx.getfile.name(), LjqType.dzljq);
            }else{
                throw new MyException("暂不支持的执行操作："+zxcz);
            }
        } catch (InvocationTargetException | IllegalAccessException e) {
            if(e.getCause()!=null&&(e.getCause() instanceof MyException)){
                throw (MyException)e.getCause();
            }
            throw new MyException(cllx+"方法执行失败",e.getCause());
        }
    }

    /**
     * 获取基础信息
     *
     * @param myParams 相关参数
     */
    public static MyParams initSjdx(MyParams myParams) {
        MyParams r = (MyParams) data(myParams, xndx, Cllx.initSjdx.name(), myParams.sys().getLjqType()).getData();
        myParams.sys().setLjqType(LjqType.dzljq);
        return r;
    }

    /**
     * 获取数据对象基础信息 <br/>
     *
     * @param myParams 相关参数
     * @return 基础信息
     * @author jingma
     */
    public static MyParams jcxx(MyParams myParams) {
        return (MyParams) data(myParams,Cllx.jcxx.name()).getData();
    }

    /**
     * 文件上传 <br/>
     * @param myParams 相关参数
     * @param files 文件列表
     * @return 基础信息
     * @author jingma
     */
    public static Result upload(MyParams myParams,MultipartFile[] files) throws Exception {
        SysSjglFile yobj = myParams.yobj(SysSjglFile.class);
        MyParams mp;
        Result r;
        List<Object> list = new ArrayList<>();
        Result result = success("", list);
        for (MultipartFile file : files) {
            yobj.setWjm(file.getOriginalFilename());
            yobj.setWjnr(Base64.getEncoder().encodeToString(file.getBytes()));
            yobj.setSclj(null);
            yobj.setId(StringUtil.getUUIDUpperStr());
            mp = JsonUtil.clone(myParams);
            mp.put(KEY_YOBJ,yobj);
            r = data(mp);
            if (!r.isStatus()) {
                return r;
            }
            list.add(r.getData());
        }
        if(list.size()>1){
            result.setMsg("上传成功" + files.length + "个文件");
        }
        return result;
    }
    /**
     * 验证规则 <br/>
     * @param myParams 相关参数
     * @author jingma
     */
    public static Result yzgz(MyParams myParams) {
        return data(myParams,Cllx.yzgz.name());
    }
    /**
     * 获取用户 <br/>
     * @param myParams 相关参数
     * @author jingma
     */
    public static SysQxYhxx getUser(MyParams myParams) {
        return data(myParams,Cllx.getUser.name()).getData(SysQxYhxx.class);
    }
    /**
     * 分页查询 <br/>
     *
     * @param myParams 相关参数
     * @return 基础信息
     * @author jingma
     */
    public static Result select(MyParams myParams) {
        return data(myParams,Cllx.select.name());
    }
    public static Result insert(MyParams myParams){
        return data(myParams,Cllx.insert.name());
    }
    public static Result update(MyParams myParams) {
        return data(myParams,Cllx.update.name());
    }
    public static Result save(MyParams myParams) {
        return data(myParams,Cllx.save.name());
    }
    /**
     * 获取sql <br/>
     *
     * @param myParams 相关参数
     * @return 0:数据载体，1：sql
     * @author jingma
     */
    public static String[] getSql(MyParams myParams) {
        return (String[]) data(myParams,Cllx.getSql.name()).getData();
    }
    /**
     * 获取sql <br/>
     *
     * @param myParams 相关参数
     * @param cllx 相关参数
     * @return 0:数据载体，1：sql
     * @author jingma
     */
    public static String[] getSql(MyParams myParams,String cllx) {
        myParams.sql().setCllx(cllx);
        return getSql(myParams);
    }
    /**
     * 结果发送到前端
     * @param myParams 相关参数
     * @param r 结果
     */
    public static void sendResult(MyParams myParams, Result r) {
        if(myParams==null){
            myParams = new MyParams();
        }
        myParams.put(KEY_YOBJ,r);
        try {
            data(myParams,Cllx.sendResult.name());
        }catch (Throwable t){
            slog.error("结果处理失败",t);
            r.addMsg("结果处理失败："+t.getMessage());
            myParams.put(KEY_SJDX,xndx);
            data(myParams,Cllx.sendResult.name());
        }
    }

    /**
     * 流式查询
     * @param sql 要执行的sq，非默认数据载体则在sql后追加;ds=xxx
     * @param pageSize 分页大小
     * @param hdff 回调方法
     * @return 执行结果
     */
    public static Result streamSelect(String sql, int pageSize, HdInterface hdff){
        return streamSelect(sql,pageSize,hdff,6000000,null);
    }
    /**
     * 流式查询
     * @param sql 要执行的sq，非默认数据载体则在sql后追加;ds=xxx
     * @param pageSize 分页大小
     * @param hdff 回调方法
     * @param timeout 超时时长，默认6000000
     * @param ctapp 穿透的app
     * @return 执行结果
     */
    public static Result streamSelect(String sql,int pageSize,HdInterface hdff,int timeout,String ctapp){
        MyParams myParams = new MyParams();
        myParams.sjdx().setDxdm("SYS_COMMON_XNDX");
        myParams.sys().setCllx(Cllx.select.name());
        myParams.page().setTotalRequired(true);
        myParams.sys().setCtapp(ctapp);
        myParams.znjh().setSql(sql);
        myParams.page().setPageSize(pageSize);
        myParams.sys().setTimeout(timeout);
        //内部调用
        MyParams jcxx = LjqManager.jcxxByParams(myParams, UserManager.findUser(valByDef(Conf.getUtilConfig().getZnjh().getUser(), UtilConst.SYS)));
        jcxx.sys().setHdcst(hdff);
        return LjqManager.data(jcxx,Cllx.select.name());
    }
    /**
     * 远程sql执行
     * @param sjzt 要执行的数据载体
     * @param sql 要执行的sql
     * @param ctapp 要穿透的应用
     * @return 执行结果
     */
    public static Result ycSqlExecute(String sjzt,String sql,String ctapp){
        return ycSqlExecute(sjzt,sql,ctapp,600000);
    }
    /**
     * 远程sql执行
     * @param sjzt 要执行的数据载体
     * @param sql 要执行的sql
     * @param ctapp 要穿透的应用
     * @param timeout 超时时长，默认600000
     * @return 执行结果
     */
    public static Result ycSqlExecute(String sjzt,String sql,String ctapp,Integer timeout){
        return ycSqlExecute(sjzt,sql,ctapp,timeout,null);
    }
    /**
     * 远程sql执行
     * @param sjzt 要执行的数据载体
     * @param sql 要执行的sql
     * @param ctapp 要穿透的应用
     * @param timeout 超时时长，默认600000
     * @param zxfs 处理方式，可以是select、update，默认自动根据sql识别
     * @return 执行结果
     */
    public static Result ycSqlExecute(String sjzt,String sql,String ctapp,Integer timeout,String zxfs){
        JSONObject yobj = new JSONObject();
        yobj.put("sjzt",sjzt);
        yobj.put("zxsql",sql);
        yobj.put("zxfs",zxfs);
        //内部调用
        return fwdy(ctapp,"SYS_TYGJ_SQLZX",Cllx.insert.name(), yobj,timeout);
    }
    /**
     * 服务调用
     * @param ctapp 穿透APP
     * @param dxdm 对象代码
     * @param cllx 处理类型
     * @param yobj 原参数
     * @return 调用结果
     */
    public static Result fwdy(String ctapp,String dxdm,String cllx,JSONObject yobj){
        return fwdy(ctapp,dxdm,cllx,yobj,null);
    }

    /**
     * 服务调用
     * @param ctapp 穿透APP
     * @param dxdm 对象代码
     * @param cllx 处理类型
     * @param yobj 原参数
     * @param timeout 请求超时时间
     * @return 调用结果
     */
    public static Result fwdy(String ctapp,String dxdm,String cllx,JSONObject yobj,Integer timeout){
        MyParams myParams = new MyParams();
        myParams.sjdx().setDxdm(dxdm);
        myParams.sys().setCllx(cllx);
        myParams.sys().setCtapp(ctapp);
        myParams.put(KEY_YOBJ,yobj);
        myParams.sys().setTimeout(timeout);
        return fwdy(myParams);
    }

    /**
     * 服务调用
     */
    public static Result fwdy(MyParams myParams){
        //内部调用
        MyParams jcxx = LjqManager.jcxxByParams(myParams, valByDef(myParams.user(),
                UserManager.findUser(valByDef(Conf.getUtilConfig().getZnjh().getUser(),UtilConst.SYS))));
        return LjqManager.data(jcxx,myParams.sys().getCllx());
    }

    /**
     * 内部上传文件
     * @param file 文件对象
     * @param user 用户信息
     * @return 结果
     */
    public static SysSjglFile upload(SysSjglFile file,SysQxYhxx user){
        MyParams jcxx = LjqManager.jcxxByAuthCode(SysAuth.QTQX.name(),user);
        jcxx.put(KEY_YOBJ, file);
        jcxx.sys().setCllx(Cllx.upload.name());
        return LjqManager.data(jcxx,Cllx.upload.name()).getData(SysSjglFile.class);
    }

    public static HttpServletResponse getHttpServletResponse() {
        return httpServletResponseITL.get();
    }

    public static void setHttpServletResponse(HttpServletResponse httpServletResponse) {
        httpServletResponseITL.set(httpServletResponse);
    }

    public static HttpServletRequest getHttpServletRequest() {
        return HttpServletRequestITL.get();
    }

    public static void setHttpServletRequest(HttpServletRequest httpServletRequest) {
        HttpServletRequestITL.set(httpServletRequest);
    }
}
